package fieldmaps.database;

import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import org.apache.log4j.Logger;
import org.postgresql.util.PGobject;

import com.vividsolutions.jts.geom.Coordinate;
import com.vividsolutions.jts.geom.Polygon;

import fieldmaps.types.RasterMapInfo;

public class DatabaseRasterMap extends DatabaseGenericMap {
	private static Logger LOGGER = Logger.getLogger(DatabaseRasterMap.class);
	
	private RasterMapInfo mapHeader;
	
	public DatabaseRasterMap() {
		this.mapHeader = new RasterMapInfo();
	}

	public static DatabaseRasterMap createMap(String mapName, RasterMapInfo mapInfo) throws IllegalArgumentException {
		
		if (mapInfo == null || mapName == null || mapName.isEmpty()) {
			throw new IllegalArgumentException();
		}
		if (DatabaseConnection.getConnectionInstance() == null) {
			throw new IllegalAccessError("DataBase connection is down!");
		}
		
		DatabaseRasterMap dbMap = new DatabaseRasterMap();
		
		try {
			PreparedStatement statement;
			
			statement = DatabaseConnection.getConnectionInstance().prepareStatement(
					"INSERT INTO map_table (MAP_NAME, MAP_TYPE) VALUES (?,?)");
	
			statement.setString(1, mapName);
			statement.setString(2, "RASTER");
			statement.execute();
			
			dbMap.loadMapId(mapName);

			statement = DatabaseConnection.getConnectionInstance().prepareStatement("INSERT INTO raster_map_info " +
					"(MAP_ID, BYTEORDER, LAYOUT, NROWS, NCOLS, NBANDS, NBITS, BANDROWBYTES, " +
					"TOTALROWBYTES, BANDGAPBYTES, NODATA, ULXMAP, ULYMAP, XDIM, YDIM) " +
					"VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)");

			statement.setInt(1, dbMap.getMapId());
			statement.setString(2, String.valueOf(mapInfo.getByteOrder()));
			statement.setString(3, mapInfo.getLayout());
			statement.setInt(4, mapInfo.getNrows());
			statement.setInt(5, mapInfo.getNcols());
			statement.setInt(6, mapInfo.getNbands());
			statement.setInt(7, mapInfo.getNbits());
			statement.setInt(8, mapInfo.getBandrowbytes());
			statement.setInt(9, mapInfo.getTotalrowbytes());
			statement.setInt(10, mapInfo.getBandgapbytes());
			statement.setInt(11, mapInfo.getNodata());
			statement.setDouble(12, mapInfo.getUlxmap());
			statement.setDouble(13, mapInfo.getUlymap());
			statement.setDouble(14, mapInfo.getXdim());
			statement.setDouble(15, mapInfo.getYdim());
			statement.execute();

			dbMap.loadHeader();
			
			return dbMap;

		} catch (SQLException e) {
			LOGGER.error("SQL Query error!: " + e.getMessage());
		}
		
		return null;
	}
	
	public static DatabaseRasterMap loadMap(String name) {
		
		if (name == null || name.isEmpty()) {
			throw new IllegalArgumentException();
		}
		if (DatabaseConnection.getConnectionInstance() == null) {
			throw new IllegalAccessError("DataBase connection is down!");
		}
		
		DatabaseRasterMap dbMap = new DatabaseRasterMap();
		
		try {
			dbMap.loadMapId(name);
			dbMap.loadHeader();
			
			return dbMap;

		} catch (SQLException e) {
			LOGGER.error(e.getMessage());
		}
		
		return null;
	}
	
	public RasterMapInfo getMapInfo () {
		return this.mapHeader;
	}
	
	private void loadHeader() throws SQLException {
		
		PreparedStatement statement = DatabaseConnection.getConnectionInstance().prepareStatement("SELECT BYTEORDER, LAYOUT, NROWS, NCOLS, NBANDS, NBITS, BANDROWBYTES, TOTALROWBYTES, BANDGAPBYTES, NODATA, ULXMAP, ULYMAP, XDIM, YDIM FROM raster_map_info WHERE map_id = ?");
		statement.setInt(1, this.getMapId());
		ResultSet rs = statement.executeQuery();
		
		if (rs.next()) {
			this.mapHeader.setByteOrder (rs.getString("BYTEORDER").charAt(0));
			this.mapHeader.setLayout (rs.getString("LAYOUT"));
			this.mapHeader.setNrows (rs.getInt("NROWS"));
			this.mapHeader.setNcols (rs.getInt("NCOLS"));
			this.mapHeader.setNbands (rs.getInt("NBANDS"));
			this.mapHeader.setNbits (rs.getInt("NBITS"));
			this.mapHeader.setBandrowbytes (rs.getInt("BANDROWBYTES"));
			this.mapHeader.setTotalrowbytes (rs.getInt("TOTALROWBYTES"));
			this.mapHeader.setBandgapbytes (rs.getInt("BANDGAPBYTES"));
			this.mapHeader.setNodata (rs.getInt("NODATA"));
			this.mapHeader.setUlxmap (rs.getDouble("ULXMAP"));
			this.mapHeader.setUlymap (rs.getDouble("ULYMAP"));
			this.mapHeader.setXdim (rs.getDouble("XDIM"));
			this.mapHeader.setYdim (rs.getDouble("YDIM"));
		}
	}

//	@Override
//	public GenericMap binaryOperation(GenericMap otherMap, ICallbackBinary callback) throws MapAlgebraException {
//		if (getBorderCoordinates().equals(otherMap.getBorderCoordinates())) {
//			throw new MapAlgebraException("Invalid maps! Both borders must be equal!");
//		}
//		
//		Polygon polygon;
//		DatabaseRasterMap newMap = DatabaseRasterMap.createMap(callback.newMapName(), mapHeader);
//		
//		startCellIteration();
//		try {
//			while ((polygon = getNextCell()) != null) {
//				List<Polygon> polygonsIntersection = new LinkedList<Polygon>();
//				List<Double> areasPolygonsIntersection = new LinkedList<Double>();
//				otherMap.getPolygonsIntersecArea(polygon, polygonsIntersection, areasPolygonsIntersection);
//				Object interpolatedValue = getInterpolatedValue (polygon, polygonsIntersection, areasPolygonsIntersection);
//				Integer userData = (Integer) callback.doOperation(interpolatedValue, polygon.getUserData());
//				newMap.addCell(polygon.getCoordinates(), userData);
//			}
//		} catch (NoSuchElementException e) {
//			LOGGER.info("New map: " + callback.newMapName() + " successfully created!");
//			return newMap;
//		} catch (SQLException e) {
//			LOGGER.error(e.getMessage());
//		}
//		resetCellIteration();
//		return null;
//	}
//
//	@Override
//	public GenericMap unaryOperation(ICallbackUnary callback) throws MapAlgebraException {
//		Polygon polygon;
//		GenericMap newMap = DatabaseRasterMap.createMap(callback.newMapName(), mapHeader);
//		
//		startCellIteration();
//		try {
//			while ((polygon = getNextCell()) != null) {
//				Integer userData = (Integer) callback.doOperation(polygon.getUserData());
//				newMap.addCell(polygon.getCoordinates(), userData);
//			}
//		} catch (NoSuchElementException e) {
//			LOGGER.info("New map: " + callback.newMapName() + " successfully created!");
//			return newMap;
//		} catch (SQLException e) {
//			LOGGER.error(e.getMessage());
//		}
//		resetCellIteration();
//		return null;
//	}

	@Override
	public Coordinate[] getBorderCoordinates() {
		Coordinate[] coordinates = new Coordinate[2];
		coordinates[0] = new Coordinate(mapHeader.getUlxmap() - mapHeader.getXdim(), mapHeader.getUlymap() + mapHeader.getYdim());
		coordinates[1] = new Coordinate(coordinates[0].x + (mapHeader.getXdim() * mapHeader.getNcols()),
				coordinates[0].y - (mapHeader.getYdim() * mapHeader.getNrows()));
		return coordinates;
	}

	@Override
	public Map<Object, Double> getWeightedNeighborsArea(Polygon polygon) throws SQLException {

		HashMap<Object, Double> aux = new HashMap<Object, Double>();
		StringBuilder sb = new StringBuilder("SELECT cell_value, ST_AREA(ST_INTERSECTION(geometry, ST_GeomFromText(");
		sb.append(polygonToText(polygon));
		sb.append("))) AS area FROM map_cells WHERE map_id = ");
		sb.append(getMapId());
		sb.append(" AND ST_INTERSECTS(geometry, ST_GeomFromText(");
		sb.append(polygonToText(polygon));
		sb.append("));");
		PreparedStatement statement = DatabaseConnection.getConnectionInstance().prepareStatement(sb.toString());
		ResultSet rs = statement.executeQuery();
		while (rs.next()) {
			double area = rs.getDouble("area");
			if (area > 0.0) {
				Object data = rs.getInt("cell_value");
				aux.put(data, area);
			}
		}
		return aux;
	}

}
